Making chat with emojis in Gameface

ui tutorials

5/30/2024

Kaloyan Geshev

Creating a chat in Gameface is a straightforward process, thanks to the built-in support for websockets. This is especially true when utilizing a library like socket.io . We have a topic in our documentation that explains how the official socket.io chat sample works seamlessly in Gameface.

Showcase overview

In this showcase, we have enhanced the socket.io chat sample by adding several features:

  • A scrollable container for messages, using the Gameface scrollable-container component , as Gameface does not natively support a scroll slider.
  • Connect and disconnect user messages displayed in the chat.
  • Timestamps for all messages.
  • Displaying the user ID for incoming messages.
  • A panel for sending emojis in the chat.

This sample focuses on how to send and show emojis in the chat, as Gameface does not currently support OS emojis natively. And it has the added bonus of allowing you to use your own custom emojis. One straightforward method is to use SVG emojis. For this sample, we used a small set of SVG emojis from OpenMoji to demonstrate the simplest way of adding emoji support to a chat in Gameface.

Source location

You can review the complete sample source within the ${Gameface package}/Samples/uiresources/UITutorials/ChatEmojis directory.

To run the sample, follow these steps:

  1. Do npm i in this directory.
  2. Run the chat server by executing node server.js in this directory.
  3. Open two or more instances of Gameface and chat between them.

Sample specifics

Chat messages container and buttons

We followed the socket.io sample to create the messages container and the send button. We then enhanced it by adding scroll functionality to the messages container using the gameface-scrollable-container and included a button for toggling the emoji panel.

index.html
1
<gameface-scrollable-container
2
automatic
3
class="messages-container"
4
>
5
<component-slot data-name="scrollable-content">
6
<ul id="messages"></ul>
7
</component-slot>
8
</gameface-scrollable-container>
9
<form
10
id="form"
11
action=""
12
>
13
<input
14
id="input"
15
autocomplete="off"
16
/>
17
<div class="chat-buttons">
18
<button class="open-emojis-panel-btn"></button>
19
<button class="send-btn">Send</button>
20
</div>
21
</form>

Sending messages

To send the text typed in the chat input to the server, an event is emitted when the Send button is clicked or when the Enter key is pressed while the input is focused.

src/index.js
1
const socket = io();
2
3
document.querySelector('.send-btn').addEventListener('click', () => {
4
if (input.value) {
5
socket.emit('chat message', input.value);
6
input.value = '';
7
}
8
});
9
10
input.addEventListener('keydown', (event) => {
11
if (event.keyCode === 13 && input.value) {
12
socket.emit('chat message', input.value);
13
input.value = '';
14
}
15
});

After the server processes the message, it emits an event to all connected users, updating their chat UI. Check the next section for details.

Receiving messages

Receiving messages from the server on the front end involves subscribing to a socket event:

src/index.js
1
socket.on('chat message', ({ msg, id }) => {
2
const item = document.createElement('li');
3
if (id === socket.id) item.classList.add('right');
4
5
item.innerHTML = (id !== socket.id ? `<div class="user-id">${id}</div>` : '') + msg;
6
messages.appendChild(item);
7
8
if (id === socket.id) {
9
waitForFrames(() => messagesContainer.onScrollSlider({ detail: { handlePosition: 100 } }), 7);
10
}
11
});

This code creates a new list element to hold the message from the server, including the user ID if the message is from another user. The element is appended to the messages container, and if the message is from the current user, the scrollable container scrolls to the bottom.

1
if (id === socket.id) {
2
waitForFrames(() => messagesContainer.onScrollSlider({ detail: { handlePosition: 100 } }), 7); // Scrolls the slider and container to 100% where the last message is
3
}

Know issue: There is a need to wait for 7 frames to ensure the scrollable container correctly scrolls to the last message. The scrollable container automatically resizes when a new list element is appended, which takes about 6 frames. Without waiting, the container might not scroll properly.

The waitForFrames function is defined as follows:

src/index.js
1
function waitForFrames(callback, frames) {
2
if (frames === 0) return callback();
3
frames--;
4
requestAnimationFrame(() => this.waitForFrames(callback, frames));
5
}

Server

On the server side, messages sent from the front end are processed by listening for socket events.

server.js
1
const express = require('express');
2
const app = express();
3
const http = require('http');
4
const server = http.createServer(app);
5
const { Server } = require("socket.io");
6
const io = new Server(server);
7
8
io.on('connection', (socket) => {
9
socket.on('chat message', (msg) => {
10
const d = new Date();
11
const timeStamp = [d.getMonth() + 1, d.getDate(), d.getFullYear()].join('/') + ' ' + [d.getHours(), d.getMinutes(), d.getSeconds()].join(':');
12
13
io.emit('chat message', { msg: `<div class="message">${msg}</div><div class="timestamp">${timeStamp}</div>`, id: socket.id });
14
});
15
});

A timestamp is generated for each message. The server then emits an event to the front end with the processed message in HTML format, along with the sender’s ID.

Chat emojis - Model

To simplify the instantiation of all emojis in a panel, we defined an array as a Gameface data binding model. Leveraging Gameface’s data binding , we can easily iterate over this model and display the emojis in the HTML page.

The model is structured as follows:

src/emojis.js
1
const emojis = [
2
'/assets/emojis/1F600.svg',
3
'/assets/emojis/1F601.svg',
4
'/assets/emojis/1F602.svg',
5
...
6
];

Chat emojis (FE) - Emojis panel

We added a button to the right of the chat input that opens a panel with all supported chat emojis, allowing users to add emojis to their messages before sending them. Selecting an emoji from the panel inserts its code into the message input, surrounded by colons. For example, :1:.

The panel was created using the gameface-tooltip component to wrap the emojis in a panel, and the gameface-scrollable-container component to enable scrolling if the emojis overflow the panel.

index.html
1
<gameface-tooltip
2
class="emojis-panel"
3
target=".open-emojis-panel-btn"
4
on="click"
5
off="click"
6
position="top"
7
>
8
<div slot="message">
9
<gameface-scrollable-container
10
class="emojis-container"
11
automatic
12
>
13
<component-slot data-name="scrollable-content">
14
<div
15
class="emoji emoji-btn"
16
data-bind-for="index,iter:{{emojis}}"
17
>
18
<div
19
data-bind-click="addEmoji({{index}})"
20
class="emoji-svg"
21
data-bind-style-background-image-url="{{iter}}"
22
></div>
23
</div>
24
</component-slot>
25
</gameface-scrollable-container>
26
</div>
27
</gameface-tooltip>

Here, we use data binding to render the emoji SVGs in the panel with data-bind-style-background-image-url="{{iter}}", which sets the background of the div to the emoji URLs from the model.

Note: The tooltip might not be positioned correctly as its target is at the bottom right of the page. To fix this, override the tooltip’s transform property:

styles.css
1
.emojis-panel {
2
...
3
transform: translateX(-70%) translateY(-105%) !important;
4
...
5
}

Chat emojis (BE) - Resolving emojis

When a message is received on the server, any emoji codes in the message should be resolved. To achieve this, extend the source in the Server section.

server.js
1
const emojis = require('./src/emojisMapServer');
2
3
io.on('connection', (socket) => {
4
socket.on('chat message', (msg) => {
5
const newMessage = msg.replace(/:[0-9]*:/g, (match) => {
6
const emojiCode = match.replace(/:/g, ''); // Retrieve the emoji code between the : symbols. For example, :1: corresponds to the emoji with index 1 in our array of emoji URLs.
7
return `<img class="emoji" src="${emojis[emojiCode]}"/>`;
8
});
9
10
const d = new Date();
11
const timeStamp = [d.getMonth() + 1, d.getDate(), d.getFullYear()].join('/') + ' ' + [d.getHours(), d.getMinutes(), d.getSeconds()].join(':');
12
13
io.emit('chat message', { msg: `<div class="message">${newMessage}</div><div class="timestamp">${timeStamp}</div>`, id: socket.id });
14
});
15
});

Here, newMessage contains the processed message with all emoji codes replaced by img elements with the corresponding emoji SVG URLs from the emojis map.

Chat user connected and disconnected

Additionally, we added messages indicating when a user connects or disconnects from the chat.

This is easily handled on the server side:

server.js
1
io.on('connection', (socket) => {
2
io.emit('user connected', socket.id);
3
4
...
5
6
socket.on('disconnect', () => {
7
io.emit('user disconnected', socket.id);
8
});
9
});

On the front end, we handle the emitted events by subscribing to the user connected and user disconnected events and reading the user ID:

src/index.js
1
function setUserState(id, connected) {
2
const item = document.createElement('li');
3
item.textContent = `User ${id} ${connected ? 'connected' : 'disconnected'}!`;
4
messages.appendChild(item);
5
}
6
7
socket.on('user connected', (id) => setUserState(id, true));
8
socket.on('user disconnected', (id) => setUserState(id, false));

For each connection or disconnection, we append a list item to the messages container.

Styles

To check the chat styles, refer to the ${Gameface package}/Samples/uiresources/UITutorials/ChatEmojis/styles.css file.

On this page